#define _GNU_SOURCE

#include <pthread.h>
#include <sys/sysinfo.h>
#include <nkfm.h>
#include <nklog.h>
#include <sched.h>
#include <stdlib.h>

#define mb()    asm volatile("mfence":::"memory")

struct nklog_state *__nklog_state;
struct nklog_ring *__nklog_ring;
int __nklog_cpu_nr;
volatile int __nklog_cpu_mask[32];


static struct nklog_state *mmap_state(int flags)
{
	int fd;
	int mode = S_IRUSR | S_IWUSR;
	int size;
	struct nklog_state *state = NULL;

	if (__nklog_state) {
		return __nklog_state;
	}

	fd = shm_open(NKLOG_STATE_PATH, flags, mode);
	if (fd < 0) {
		nkfm_print("Open file %s failed \n", NKLOG_STATE_PATH);
		return NULL;
	}

	size = sizeof(*state);
	ftruncate(fd, size);

	state = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (state == MAP_FAILED) {
		nkfm_print("Map file %s failed\n", NKLOG_STATE_PATH);
		return NULL;
	}

	state->state = NKLOG_STATE_SLEEP;

	return state;
}

static struct nklog_ring *mmap_ring(int flags)
{
	int fd;
	int mode = S_IRUSR | S_IWUSR;
	int size;
	struct nklog_ring *ring;

	__nklog_cpu_nr = get_nprocs();
	size = sizeof(*ring) * __nklog_cpu_nr;

	fd = shm_open(NKLOG_RING_PATH, flags, mode);
	if (fd < 0) {
		nkfm_print("Open file %s failed\n", NKLOG_RING_PATH);
		return NULL;
	}

	ftruncate(fd, size);

	ring = mmap(NULL, size,	PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (ring == MAP_FAILED) {
		nkfm_print("Mmap log ring failed\n");
		return NULL;
	}

	if (flags & O_CREAT) {
		memset(ring, 0, size);
	}

	return ring;
}

static int log_init(int flags)
{
	__nklog_state = mmap_state(flags);
	if (!__nklog_state) {
		return -1;
	}

	__nklog_ring = mmap_ring(flags);
	if (!__nklog_ring) {
		return -1;
	}

	return 0;
}

static void *sync_tsc_thread(void *arg)
{
	int cpu;
	struct nklog_ring *ring;

	cpu = *((int *)arg);

	ring = __nklog_ring + cpu;

	nkfm_sched(cpu);

	__nklog_cpu_mask[cpu] = 1;

	while (!__nklog_cpu_mask[0]);

	ring->base_tsc = rdtsc();

	__nklog_cpu_mask[cpu] = 0;

	free(arg);

	pthread_detach(pthread_self());
	pthread_exit(NULL);
}

static void sync_tsc(int cpu_nr)
{
	int i;
	struct nklog_ring *ring;

	ring = __nklog_ring;

	for (i = 1; i < cpu_nr; i++) {
		pthread_t tid;
		int *arg;
		int ret;

		arg = malloc(sizeof(*arg));
		if (!arg) {
		}

		*arg = i;
		ret = pthread_create(&tid, NULL, sync_tsc_thread, arg);	
		if (ret) {
		}
	}
	
	nkfm_sched(0);

	for (i = 1; i < cpu_nr; i++) {
		while (!__nklog_cpu_mask[i]);
	}

	__nklog_cpu_mask[0] = 1;
	ring->base_tsc = rdtsc();

	for (i = 1; i < cpu_nr; i++) {
		while (__nklog_cpu_mask[i]);
	}
}

int nklog_init(void)
{
	return log_init(O_RDWR);
}

int nklog_init_create(void)
{
	log_init(O_RDWR | O_CREAT | O_TRUNC);

	sync_tsc(__nklog_cpu_nr);

	return 0;
}

int nklog_write(int mid, const char *msg)
{
	int w;
	struct nklog_buf *buf;
	struct nklog_ring *ring;
	int cpu;

	if (!msg) {
		return -1;
	}

	cpu = sched_getcpu();
	if (cpu >= __nklog_cpu_nr || cpu < 0) {
		return -1;
	}

	ring = __nklog_ring + cpu;

	w = ring->w;

	if ((w + 1) % NKLOG_MAX_BUF_NR == ring->r) {
		nkfm_print("Ring is full\n");
		return -1;
	}

	buf = ring->buf + w;

	buf->mid = mid;
	buf->time = __sync_fetch_and_add(&__nklog_state->time, 1);
	buf->time2 = rdtsc() - ring->base_tsc;
	snprintf(buf->log, sizeof(buf->log), "%s", msg);

	ring->w = (w + 1) % NKLOG_MAX_BUF_NR;

	return 0;
}

int nklog_read(int fd, int cpu)
{
	struct nklog_ring *ring;
	int r;
	char data[1024];
	struct nklog_buf *buf;

	if (fd < 0) {
		return -1;
	}

	if (cpu >= __nklog_cpu_nr || cpu < 0) {
		return -1;
	}

	ring = __nklog_ring + cpu;

	r = ring->r;
	if (r == ring->w) {
		return 0;
	}

	buf = ring->buf + r;
	snprintf(data, sizeof(data), "[%d][%llu][%llu] %s", cpu, buf->time, buf->time2, buf->log);

	lseek(fd, 0, SEEK_END);
	write(fd, data, strlen(data));

	ring->r = (r + 1) % NKLOG_MAX_BUF_NR;

	return 0;
}

static int check_read(int read)
{
	struct nklog_ring *ring;

	ring = __nklog_ring + read;

	return (ring->w != ring->r);
}

int nklog_read_all(int fd)
{
	int i;
	int j;
	int cpu;
	int flag = 0;
	int limit = 0;

	while (limit++ < 10000) {
		for (i = 0; i < __nklog_cpu_nr; i++) {
			if (check_read(i)) {
				break;
			}
		}

		if (i >= __nklog_cpu_nr) {
			break;
		}

		cpu = i;

		for (j = 0; j != i && j < __nklog_cpu_nr; j++) {
			struct nklog_ring *r1;
			struct nklog_ring *r2;

			if (!check_read(j)) {
				continue;
			}

			r1 = __nklog_ring + cpu;
			r2 = __nklog_ring + j;

			if (r1->buf[r1->r].time > r2->buf[r2->r].time) {
				cpu = j;
			}
		}
		
		nklog_read(fd, cpu);
		flag = 1;
	}

	if (flag)
		nkfm_print("Read Done\n");

	return 0;
}


